<?php
/**
 * Core_Record
 * 
 * @author naspeh
 * @version $Id: Record.php 661 2008-06-16 10:38:02Z naspeh $
 */
class Core_Record
{
    /**
     * Объект источника данных
     *
     * @var Core_Record_Source_Abstract
     */
    protected $_source = null;
    /**
     * Связной Acl ресурс
     *
     * @var Core_Acl_Resource_Abstract
     */
    protected $_aclResource;
    /**
     * Уникальный ключ для записи
     *
     * @var string
     */
    protected $_keyRow = array('key' => 'id');
    /**
     * Объект сортировки
     *
     * @var Core_Record_Order
     */
    protected $_order = null;
    /**
     * Связная форма
     *
     * @var Core_Form
     */
    protected $_form = null;
    /**
     * Массив выводимых полей
     *
     * @var array
     */
    protected $_fields = array();
    /**
     * Массив кнопок(действий)
     *
     * @var array
     */
    protected $_actions = array();
    /**
     * Разрешенные действия
     *
     * @var array
     */
    protected $_isAllowActions = array();
    /**
     * Массив действий для записи
     *
     * @var array
     */
    protected $_actionsRow = null; 
    /**
     * Класс связного Acl ресурса для записи
     *
     * @var string
     */
    protected $_aclResourceRowClass;
    /**
     * Флаг наличия действий для записи
     *
     * @var boolean
     */
    protected $_isActionsRow = false;
    /**
     * Результирующие ключи
     *
     * @var array
     */
    protected $_extractIds = array();
    /**
     * Конструктор
     *
     */
    public function __construct()
    {
        $this->init();
        $this->_order = new Core_Record_Order($this);
        $this->fetchAll();
    }
    /**
     * Начальные установки
     *
     * @return Core_Record
     */
    public function init()
    {
        return $this;        
    }
    /**
     * Установка флага наличия действий для записи
     *
     * @param boolean $flag
     * @return Core_Record
     */
    public function setIsActionsRow($flag)
    {
        $this->_isActionsRow = (bool) $flag;
        return $this;
    }
    /**
     * Возвращает флаг наличия действий для записи
     *
     * @return boolean
     */
    public function isActionsRow()
    {
        return $this->_isActionsRow;
    }
    /**
     * Возвращает класс связного Acl ресурса для записи
     *
     * @return string
     */
    public function getAclResourceRowClass()
    {
        return $this->_aclResourceRowClass;
    }
    /**
     * Устанавливает класс связного Acl ресурса для записи
     *
     * @param string $className
     * @return Core_Record
     */
    public function setAclResourceRowClass($className)
    {
        $this->_aclResourceRowClass = (string) $className;
        return $this;                     
    }
    /**
     * Устанавливает уникальный ключ для записи
     *
     * @param string $key
     * @param string $alias 
     * @return string
     */
    public function setKeyRow($key, $alias = null)
    {
        $key = (string) $key;
        if (false !== $this->getFields($key)) {
        	throw new Core_Exception('keyRowNotFoundInFieldsList# ' . $key);
        }
        $this->_keyRow['key'] = $key;        
        if (isset($alias)) {

            $this->_keyRow['alias'] = $alias;
        }
        return $this;
    }
    /**
     * Возвращает уникальный ключ для записи
     *
     * @param boolean $getAlias
     * @return string
     */
    public function getKeyRow($getAlias = false)
    {
        if ($getAlias && isset($this->_keyRow['alias'])) {
        	return $this->_keyRow['alias'];
        } else {
        	return $this->_keyRow['key'];
        }
    }
    /**
     * Возвращает объект сортировки
     *
     * @return Core_Record_Order
     */
    public function getOrder()
    {
        return $this->_order;
    }
    /**
     * Добавляет поле
     *
     * @param string $name
     * @param strimg $options
     * @return Core_Record
     */
    public function addField($name, $options = null)
    {
        if ($this->getSource()->isValidField($name)) {
        	$field = new Core_Record_Field();
            $field->setName($name);
            if (isset($options)) {
                $field->setOptions($options);
            } else {
                $field->setTitle($name);
            }
            $this->_fields[$name] = $field;
        }
        return $this;    
    }
    /**
     * Возвращает массив объектов полей (объект поля)
     *
     * @param string $name
     * @return Core_Record_Field|array|false
     */
    public function  getFields($name = null)
    {
        if (!is_null($name)) {
            if (isset($this->_fields[$name])) {
            	return $this->_fields[$name];
            }
            return false;
        }
        return $this->_fields;
    }
    /**
     * Добавляет все допустимые поля
     *
     * @return Core_Record
     */
    public function addAllFields()
    {
        foreach ($this->getSource()->getFields() as $field) {
        	$this->addField($field);
        }
        return $this;        
    }
    /**
     * Добавляет поля
     *
     * @param array|string $fields
     * @return Core_Record
     */
    public function addFields($fields)
    {
        if (!is_array($fields)) {
            $fields = array((string) $fields);
        }
        foreach ($fields as $field) {
            $options = null;
            if (is_array($field)) {
            	$options = current($field);
            	$field = key($field); 
            }
            $this->addField($field, $options);
        }
        return $this;  
    }
    /**
     * Удаляет поля(поле)
     *
     * @param array|string $fields
     * @return Core_Record
     */
    public function removeFields($fields)
    {
        if (!is_array($fields)) {
        	$fields = array((string) $fields);
        }
        foreach ($fields as $field) {
        	if (false !== $this->getFields($field)) {
        		unset($this->_fields[$field]);
        	}
        }
        return $this;  
    }
    /**
     * Возвращает кол-во полей
     *
     * @return integer
     */
    public function countFields()
    {
        return count($this->_fields);
    }
    /**
     * Добавляет кнопку(действие)
     *
     * @param string $name
     * @param string $action
     * @param array $options
     * @return Core_Record
     */
    public function addAction($name, $action, array $options = null)
    {
        $name = (string) $name;
        $action = (string) $action;
        $this->_actions[$name] = array(
            'name' => $name,
            'action' => $action
        );
        if (!isset($options['type'])) {
            $options['type'] = 'submit'; 
        }
        if (!isset($options['label'])) {
            $options['label'] = $name;
        }
        if (!isset($options['attribs'])) {
            $options['attribs'] = array(
                'class' => 'positive'
            ); 
        }
        if (!isset($options['decorators'])) {
            $options['decorators'] = array(
                'ViewHelper',
                array(array('data' => 'HtmlTag'), array('tag' => 'div')),
            ); 
        }
        $this->getForm()->addElement('button', $name, $options);        
        return $this;
    }
    /**
     * Возвращает действия(е)
     *
     * @param string $name
     * @return array|false
     */
    public function getActions($name = null)
    {
        if (isset($name)) {
        	if (isset($this->_actions[$name])) {
        		return $this->_actions[$name];
        	}
        	return false;
        }
        return $this->_actions;
    }
    /**
     * Возвращает флаг наличия разрешенных дейтвий
     *
     * @param string $actionName
     * @return boolean
     */
    public function isAllowActions($actionName = null)
    {
        if (isset($actionName)) {
        	if (in_array($actionName, $this->_isAllowActions)) {
        		return true;
        	}
        	return false;
        }
        
        if (empty($this->_isAllowActions)) {
        	return false;
        }
        return true;
    }
    /**
     * Добавление разрешенного дейтвия
     *
     * @param string $actionName
     * @return Core_Record
     */
    public function addIsAllowAction($actionName)
    {
        if (false === $this->getActions($actionName)) {
            throw new Core_Exception('wrongAction# ' . $actionName);
        }        
        $this->_isAllowActions[] = (string) $actionName;
        return $this;
    }
    /**
     * Очищает разрешения на действия
     *
     * @return Core_Record
     */
    public function clearIsAllowActions()
    {
        $this->_isAllowActions = array();
        return $this;
    }
    /**
     * Добавляет кнопку(действие)
     *
     * @param string $name
     * @param string $action
     * @param array $options
     * @return Core_Record
     */
    public function addActionForRow($name, $action, array $options = null)
    {
        $name = (string) $name;
        $action = (string) $action;
        $this->_actionsRow[$name] = array(
            'name' => $name,
            'action' => $action,
            'option' => $options
        );
        return $this;
    }
    /**
     * Возвращает действия(е)
     *
     * @param string $name
     * @return array|false
     */
    public function getActionsForRow($name = null)
    {
        if (isset($name)) {
            if (isset($this->_actionsRow[$name])) {
                return $this->_actionsRow[$name];
            }
            return false;
        }
        return $this->_actionsRow;
    }
    /**
     * Возвращает источник для списка 
     *
     * @param mixed $source
     * @return Core_Record_Source_Abstract
     */
    public static function factory($source)
    {
        if ($source instanceof Zend_Db_Table_Abstract) {
            $source = new Core_Record_Source_Table($source);
        } elseif ($source instanceof Zend_Db_Select) {
            $source = new Core_Record_Source_Select($source);
        } else {
            throw new Core_Exception('unknownSourceType# ');
        }
        return $source;
    }
    /**
     * Устанавливает источник данных
     *
     * @param mixed $source
     * @return Core_Record
     */
    public function setSource($source)
    {
        $this->_source = self::factory($source);
        return  $this;
    }
    /**
     * Возвращает объект источника данных
     *
     * @return Core_Record_Source_Abstract
     */
    public function getSource()
    {
        if (is_null($this->_source)) {
        	throw new Core_Exception('sourceNotInit#');
        }
        return $this->_source;
    }
    /**
     * Формирует вывод
     *
     * @return string
     */
    public function render()
    {
        $rowRecord = new Core_Record_Row($this);
        //@todo почему subForm "ids" в форме уже присутствует до вызова $this->fetchAll()        
        $rowset = $this->fetchAll();
        $this->getForm()->initElements();
        $content = '';
        $even = 1;        
        foreach ($rowset as $row) {
            $even = 1 - $even;
            $rowRecord->setIsEven($even); 
            $rowRecord->setResource($row);
        	$content .= $rowRecord->render();
        }
        if ('' == $content) {
        	$content = '<tr><td colspan="' . $this->countFields() . '">Список пуст</td></tr>';
        	$this->_order = null;
        }
        
        $content = $rowRecord->renderHeader() . $content;
        $content = '<table>' . PHP_EOL . $content . PHP_EOL . '</table>';
        
        if ($this->isAllowActions()) {
            $content = $this->renderForm($content);
        }
        return $content;
    }
    /**
     * Формирует вывод связной формы
     *
     * @param string $content
     * @return string
     */
    public function renderForm($content)
    {
        $content = $this->getForm()->getElement('_first')->render() . $content;
        $contentButtons = '';
        foreach ($this->getActions() as $action) {
            if ($this->isAllowActions($action['name'])) {
            	$contentButtons .= $this->getForm()->getElement($action['name'])->render();
            }
        }
        $content .= '<tr><td>' . $contentButtons . '</td></tr>';
        foreach ($this->getForm()->getDecorators() as $decorator) {
            $decorator->setElement($this->getForm());
            $content = $decorator->render($content);
        }
        return $content;        
    }
    /**
     * Serialize as string
     *
     * Proxies to {@link render()}.
     * 
     * @return string
     */
    public function __toString()
    {
        try {
            return  $this->render();
        } catch (Exception $e) {
            trigger_error($e->getMessage(), E_USER_WARNING);
            return '';
        }
    }
    /**
     * Вовращает все строки
     *
     * @return array
     */
    public function fetchAll($fromSave = true)
    {
        $index = get_class($this) . ':fetchAll';
        if (Zend_Registry::isRegistered($index) && $fromSave) {
        	return Zend_Registry::get($index);
        }
        if (null !== ($order = $this->getOrder())) {
        	$order = $order->get();
        }
        $rowset = $this->getSource()->fetchAll(null, $order);
        $result = array();
        $acl = Core_Acl::getRegisrty();
        foreach ($rowset as $row) {
            if (is_null($this->getAclResourceRowClass())) {
                $result[$row['id']] = $row;
                continue;
            }
            $class = $this->getAclResourceRowClass();
            $resource = new $class($row);
            $acl->active($resource);
            if ($acl->isAllowed(Core_Auth::get('id'), $resource)) {
                $result[$resource->getResourceId()] = $row;
                $allowPrivileges = $acl->getAllowPrivileges(Core_Auth::get('id'), $resource);
                foreach ($this->getActions() as $action) {
                    if (isset($allowPrivileges[$action['name']]) && !$this->isAllowActions($action['name'])) {
                        $this->addIsAllowAction($action['name']);
                    }
                }
            }
        }
        if ($this->isAllowActions()) {
            $form = new Core_Form(array('withHash' => false));
            foreach ($result as $key => $row) {
                $key = (string) $key;
                $element = new Zend_Form_Element_Checkbox($key);
                $element->setDecorators(array(
                    'ViewHelper'
                ));
                $form->addElement($element);
            }
            $this->getForm()->addSubForm($form, 'ids');
        }
        Zend_Registry::set($index, $result);
        return $result;
    }
    /**
     * Обновляет список
     *
     * @return Core_Record
     */
    public function refresh()
    {
        $this->clearIsAllowActions();
        $this->fetchAll(false);
        return $this;
    }
    /**
     * Возвращает объект формы
     *
     * @return Core_Form
     */
    public function getForm()
    {
        if (is_null($this->_form)) {
            $form = new Core_Form();
            $form->setDecorators(array(
                'Form',
            ));
            $this->_form = $form;
        }
        return $this->_form;
    }
    /**
     * Выполняет действие, активированое кнопкой
     *
     * @param Zend_Controller_Request_Http $request
     * @return Core_Record
     */
    public function process(Zend_Controller_Request_Http $request)
    {
        if (!$this->isAllowActions()) {
            return $this;
        }
        if ($this->getForm()->isValid($request)) {
            $values = $this->getForm()->getValues(true);
            $values = array_diff($values, array(null));
            $nameAction  = array_intersect_key($values, $this->getActions());
            if (1==count($nameAction)) {
                $nameAction = key($nameAction);
                $action = $nameAction . 'Action';
                if (method_exists($this, $action)) {
                    $this->$action();
                    if (0 < count($this->getExtractIds($nameAction))) {
                        $this->refresh();
                    }
                    
                } else {
                    throw new Core_Exception('actionNotFoundForRecord# '.$nameAction);
                }
            }
        }
        return $this;
    }
    /**
     * Возвращает результирующие ключи
     *
     * @param string $action
     * @return array
     */
    public function getExtractIds($action = null)
    {
        if (!isset($action)) {
            $action = key($this->getActions());
        }
        if (!isset($this->_extractIds[$action])) {
        	$this->_extractIds[$action] = $this->extractIds($action);
        }
        return $this->_extractIds[$action];
    }
    /**
     * Выбирает активные Id
     *
     * @return array
     */
    private function extractIds($action)
    {
        $ids = array();
        $acl = Core_Acl::getRegisrty();
        $values = $this->getForm()->getSubForm('ids')->getValues(true);
        $privilege = $this->getActions($action);
        $count = 0;
        foreach ($values as $id => $value) {
            if ($value) {
                $count++;
                if (is_null($this->getAclResourceRowClass())) {
              		$ids[] = $id;
                	continue;
                }
                if (false !== $acl->getAllowPrivileges(Core_Auth::get('id'), $id, $privilege['name'])) {
                    $resource = $acl->get($id);
                    $ids[] = $resource->id;
                }
            }
        }
        if (0 == $count) {
        	Core_Message::add('Действие не выполнено не для одной записи <b>(не отмечены)</b>', Core_Message::TYPE_NOTICE);
        } elseif (0 == count($ids)) {
        	Core_Message::add('Действие не выполнено не для одной записи <b>(не достаточно прав)</b>', Core_Message::TYPE_NOTICE);
        } elseif ($count !== count($ids)) {
        	Core_Message::add('Действие выполнено не для всех записей <b>(не достаточно прав)</b>', Core_Message::TYPE_NOTICE);
        } else {
        	Core_Message::add('Действие выполнено для всех записей', Core_Message::TYPE_SUCCESS);
        }
        return $ids;
    }
}